package com.yzh.luckydraw.infrastructure.repository;

import com.yzh.luckydraw.common.Constants;
import com.yzh.luckydraw.domain.activity.model.aggregates.ActivityInfoLimitPageRich;
import com.yzh.luckydraw.domain.activity.model.req.ActivityInfoLimitPageReq;
import com.yzh.luckydraw.domain.activity.model.req.PartakeReq;
import com.yzh.luckydraw.domain.activity.model.res.StockResult;
import com.yzh.luckydraw.domain.activity.model.vo.*;
import com.yzh.luckydraw.domain.activity.repository.IActivityRepository;
import com.yzh.luckydraw.infrastructure.dao.*;
import com.yzh.luckydraw.infrastructure.po.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 活动表仓储服务
 */
@Repository
public class ActivityRepository implements IActivityRepository {
    private Logger logger = LoggerFactory.getLogger(ActivityRepository.class);

    @Resource
    private IActivityDao activityDao;
    @Resource
    private IAwardDao awardDao;
    @Resource
    private IStrategyDao strategyDao;
    @Resource
    private IStrategyDetailDao strategyDetailDao;
    @Resource
    private IUserTakeActivityCountDao userTakeActivityCountDao;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;


    @Override
    public void addActivity(ActivityVO activityVO) {
        Activity activity = new Activity();
        BeanUtils.copyProperties(activityVO, activity);
        activityDao.insert(activity);

        //设置互动库存
        redisTemplate.opsForValue().set(Constants.RedisKey.KEY_LUCKYDRAW_ACTIVITY_STOCK_COUNT(activity.getActivityId()), 0);
    }

    @Override
    public void addAward(List<AwardVO> awardList) {
        List<Award> list = new ArrayList<>();
        for (AwardVO awardVO : awardList) {
            Award award = new Award();
            BeanUtils.copyProperties(awardVO, award);
            list.add(award);
        }
        awardDao.insertList(list);
    }

    @Override
    public void addStrategy(StrategyVO strategyVO) {
        Strategy strategy = new Strategy();
        BeanUtils.copyProperties(strategyVO, strategy);
        strategyDao.insert(strategy);
    }

    @Override
    public void addStrategyDetailList(List<StrategyDetailVO> strategyDetailList) {
        List<StrategyDetail> list = new ArrayList<>();
        for (StrategyDetailVO strategyDetailVO : strategyDetailList) {
            StrategyDetail strategyDetail = new StrategyDetail();
            BeanUtils.copyProperties(strategyDetailVO, strategyDetail);
            list.add(strategyDetail);
        }
        strategyDetailDao.insertList(list);
    }

    @Override
    public boolean alterState(Long activeId, Enum<Constants.ActivityState> beforeState, Enum<Constants.ActivityState> afterState) {
        /**
         * 这里传入beforeState初始状态，它会写在sql语句where后，防止并发情况其他线程修改。就是乐观锁的思想，
         * 还有就是有多个修改状态请求，要判断前置请求是否给予通过，比如待审核等等
         * 并且activeId是索引，还能保证行级锁
         */
        AlterStateVO alterStateVO = new AlterStateVO(activeId, ((Constants.ActivityState) beforeState).getCode(), ((Constants.ActivityState) afterState).getCode());
        int res = activityDao.alterState(alterStateVO);
        return res == 1;
    }

    @Override
    public ActivityBillVO queryActivityBill(PartakeReq req) {
        //查询活动信息
        Activity activity = activityDao.queryActivityById(req.getActivityId());

        //从缓存中获取库
        Object userStockCountObj = redisTemplate.opsForValue().get(Constants.RedisKey.KEY_LUCKYDRAW_ACTIVITY_STOCK_COUNT(req.getActivityId()));

        //查询活动领取次数信息
        UserTakeActivityCount userTakeActivityCount = new UserTakeActivityCount();
        userTakeActivityCount.setuId(req.getuId());
        userTakeActivityCount.setActivityId(req.getActivityId());
        UserTakeActivityCount userTakeActivityCountRes = userTakeActivityCountDao.queryUserTakeActivityCount(userTakeActivityCount);
        //封装结果信息
        ActivityBillVO activityBillVO = new ActivityBillVO();
        activityBillVO.setuId(req.getuId());
        activityBillVO.setActivityId(req.getActivityId());
        activityBillVO.setActivityName(activity.getActivityName());
        activityBillVO.setBeginTime(activity.getBeginDateTime());
        activityBillVO.setEndTime(activity.getEndDateTime());
        activityBillVO.setStockCount(activity.getStockCount());
        activityBillVO.setStockSurplusCount(null == userStockCountObj ? activity.getStockSurplusCount() : activity.getStockCount() - (Integer) userStockCountObj);
        activityBillVO.setTakeCount(activity.getTakeCount());
        activityBillVO.setState(activity.getState());
        activityBillVO.setStrategyId(activity.getStrategyId());
        activityBillVO.setUserTakeLeftCount(null == userTakeActivityCountRes ? null : userTakeActivityCountRes.getLeftCount());

        return activityBillVO;
    }

    @Override
    public int subtractionActivityStock(Long activityId) {
        return activityDao.subtractionActivityStock(activityId);
    }

    @Override
    public StockResult subtractionActivityStockByRedis(String uId, Long activityId, Integer stockCount, Date endTime) {
        //1.获取抽奖活动库存key
        String stockKey = Constants.RedisKey.KEY_LUCKYDRAW_ACTIVITY_STOCK_COUNT(activityId);

        //2.扣减库存，占用库存数
        Integer stockUsedCount = redisTemplate.opsForValue().increment(stockKey, 1).intValue();

        //3.超出库存判断，恢复原始库存(stockCount就是activity表的字段值，stockUsedCount是redis的key值)
        if (stockUsedCount > stockCount) {
            redisTemplate.opsForValue().increment(stockKey, -1);
            return new StockResult(Constants.ResponseCode.OUT_OF_STOCK.getCode(), Constants.ResponseCode.OUT_OF_STOCK.getInfo());
        }

        //4.以活动库存占用编号，生成对应加锁key，细化锁的颗粒度   
        String stockTokenKey = Constants.RedisKey.KEY_LUCKYDRAW_ACTIVITY_STOCK_COUNT_TOKEN(activityId, stockUsedCount);

        //5.使用setNx加分布式锁，以活动结束时间，设置锁的有效时间
        long millisecond = endTime.getTime() - System.currentTimeMillis();
        boolean lockToken = (boolean) redisTemplate.execute((RedisCallback) connection -> {
            //获取锁setNX(key, 超时时间)，这里millisecond + 1 是因为时间戳精度是一秒
            return connection.setNX(stockTokenKey.getBytes(), String.valueOf(System.currentTimeMillis() + millisecond + 1).getBytes());
        });
        /*
         * 如果分布式锁操作失败则携带stockTokenKey返回，外层会删除该key
         * 其实删除key只是为了减少redis内存占用而已，因为stockKey的库存已经变化了，这里就算删除key下次也不会执行此次库存的操作了。
         * 这里没办法进行回滚操作，例如库存96、97、98、99(stockUsedCount)被获取。库存96获取分布式锁失败，理论上想回滚到95(因为库存数是incr+1递增的，然后和实际库存去对比是否超卖，所以这里回滚就是95)
         * 但是stockUsedCount这个值已经增长到99了，所以没办法回滚。
         * 这里只保证不超卖，但不保证库存消耗完。
         */
        if (!lockToken) {
            logger.info("抽奖活动{} 用户秒杀{} 扣减库存，分布式锁失败：{}", activityId, uId, stockTokenKey);
            return new StockResult(Constants.ResponseCode.ERR_TOKEN.getCode(), Constants.ResponseCode.ERR_TOKEN.getInfo(), stockTokenKey);
        }
        return new StockResult(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo(), stockTokenKey, stockCount - stockUsedCount);
    }

    @Override
    public void recoverActivityCacheStockByRedis(Long activityId, String tokenKey, String code) {
        //删除分布式锁
        if (tokenKey != null) {
            redisTemplate.delete(tokenKey);
        }
    }

    @Override
    public List<ActivityVO> scanToDoActivityList(Long id) {
        List<Activity> activityList = activityDao.scanToDoActivityList(id);
        List<ActivityVO> activityVOList = new ArrayList<>();
        for (Activity activity : activityList) {
            ActivityVO activityVO = new ActivityVO();
            activityVO.setId(activity.getId());
            activityVO.setActivityId(activity.getActivityId());
            activityVO.setActivityName(activity.getActivityName());
            activityVO.setBeginDateTime(activity.getBeginDateTime());
            activityVO.setEndDateTime(activity.getEndDateTime());
            activityVO.setState(activity.getState());
            activityVO.setCreator(activity.getCreator());
            activityVOList.add(activityVO);
        }
        return activityVOList;
    }

    @Override
    public ActivityInfoLimitPageRich queryActivityInfoLimitPage(ActivityInfoLimitPageReq req) {
        Long count = activityDao.queryActivityInfoCount(req);
        List<Activity> activities = activityDao.queryActivityInfoList(req);

        List<ActivityVO> activityVOList = new ArrayList<>();
        for (Activity activity : activities) {
            ActivityVO activityVO = new ActivityVO();
            activityVO.setId(activity.getId());
            activityVO.setActivityId(activity.getActivityId());
            activityVO.setActivityName(activity.getActivityName());
            activityVO.setActivityDesc(activity.getActivityDesc());
            activityVO.setBeginDateTime(activity.getBeginDateTime());
            activityVO.setEndDateTime(activity.getEndDateTime());
            activityVO.setStockCount(activity.getStockCount());
            activityVO.setStockSurplusCount(activity.getStockSurplusCount());
            activityVO.setTakeCount(activity.getTakeCount());
            activityVO.setStrategyId(activity.getStrategyId());
            activityVO.setState(activity.getState());
            activityVO.setCreator(activity.getCreator());
            activityVO.setCreateTime(activity.getCreateTime());
            activityVO.setUpdateTime(activity.getUpdateTime());
            activityVOList.add(activityVO);
        }
        return new ActivityInfoLimitPageRich(count, activityVOList);
    }
}
